# include<stdio.h>
# include<assert.h>
# include<stdlib.h>
# include<assert.h>
# include<sys/types.h>
# include<sys/stat.h>
# include<fcntl.h>
# include<sys/wait.h>
# include<unistd.h>
# include<string.h>
# include<ctype.h>

// 实现一个自己的简单的shell
#define NUM 1024
#define OPTION_NUM 32

// 定义重定向状态
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3 

char lineCommand[NUM]; // 大小是NUM，这里是全局变量
char* myargc[OPTION_NUM]; // 指针数组，用于存在字符串切割后的选项
int redirtype = NONE_REDIR; // 设置重定向状态
char* redirFile = NULL;

// 定义一个宏函数，用于过滤多个空格
#define trimSpace(start) do{\
        while(isspace(*start)) ++start;\
    }while(0) // 不需要加; 当放到条件时也不会报错

void CommandCheck(char* command)
{
    // 先判断是不是重定向，是什么重定向
    assert(command != NULL);
    char* start = command;
    char* end = command + strlen(command); // 指向末尾
    
    while(start < end)
    {
        if(*start == '>')
        {
            // 可能是追加，也可能是输出重定向
            *start = '\0'; 
            start++;
            if(*start == '>')
            {
                // 追加重定向
                redirtype = APPEND_REDIR;
                start++; 
            }
            else
            {
                // 输出重定向
                redirtype = OUTPUT_REDIR;
            }
            trimSpace(start); //找到文件
            redirFile = start;
            break;
        }
        else if(*start == '<')
        {
            // 输入重定向
            *start = '\0'; // 左边为指令，右边为文件
            start++;
            trimSpace(start); // 过滤所有空格，直到碰到文件
            redirtype = INPUT_REDIR;
            redirFile = start;
            break;
        }
        start++; // 迭代
    }
    // 走出来就是没有重定向,但是redirtype初始化就是NONE_REDIR
}


int main()
{
    while(1)
    {
        // 每次进来都要重置有关重定向的变量，redirtype， redirFile 
        redirtype = NONE_REDIR;
        redirFile = NULL; 

        // shell就需要有一个输出提示符
        printf("wzf@主机名 当前路径# "); // 这里不需要\n，输入指令是在#后面的
        fflush(stdout); //没有\n 用fflush刷新缓冲区

        //获取用户的命令
        char* s = fgets(lineCommand, sizeof(lineCommand)-1, stdin);
        assert(s != NULL);

        // 用户每次输入完一个命令之后，都会输入一个回车键作为结尾
        lineCommand[strlen(lineCommand)-1] = 0; // 将输入abcd\n中的\n给取消掉了
    
        //printf("receive : %s\n", lineCommand); // 测试是否正常接受到用户的指令
        
        // 判断用户是否执行的是重定向指令，三种情况【输出、追加、输入重定向】
        CommandCheck(lineCommand);
        // 判断完之后，若有重定向，则做出处理,让子进程去处理
        

        // 将用户输入的指令，判断是否带选项，带选项的话要切割并放进myargc中
        // "ls -a -l" 要切割成 "ls" "-a" "-l"
        myargc[0] = strtok(lineCommand, " "); // myargc的第一个选项是要执行什么命令
        //切割该命令所带的选项
        //myargc[1]开始装选项，这里myargc[end]必须是NULL
        int i = 1;
        // 这里要注意，如果要继续在已经被切割的原来的字符串上面继续切割，就要传一个NULL
        while(myargc[i++] = strtok(NULL, " ")); // 切到不能切了就返回null，刚好循环停止，这个循环一直检测myargc[i++]
        
        // cd命令的处理【这种不需要子进程执行的命令，我们叫做内建命令】
        if(myargc[0] != NULL && strcmp(myargc[0], "cd") == 0)
        {
            // 如果是cd命令，那么一定会跟一个路径，因此myargc[1]一定不为空
            if(myargc[1] != NULL)
                chdir(myargc[1]); // 更改父进程当前路径
            continue; //直接结束本次shell解析，进行下一次，cd目的已经完成
        }

    // 为了测试是否正确切割并放入myargc，这里可以用条件编译
#ifdef DEBUG
    for(int i = 0; myargc[i]; i++)
    {
        printf("myargc[%d]: %s\n", i, myargc[i]);
    }
#endif

        //此时已经处理好，用户所输入的指令了，这个时候就要去执行命令
        // 执行命令可以用进程的程序替换
        pid_t id = fork();
        assert(id != -1);
        if(id == 0)
        {
            // child
            // 程序替换的函数选择execvp，环境变量用系统的，将处理好的命令直接传给execvp即可
            // 这样只用传要执行什么指令，以及其指令所带的选项即可
            
            // 判断是否有重定向，是什么重定向, 这里的重定向不会影响到父进程`:`
            switch(redirtype)
            {
                case NONE_REDIR:
                    break;
                case INPUT_REDIR:
                    {
                        int fd = open(redirFile, O_RDONLY, 0666);
                        if(fd < 0)
                        {
                            perror("open error");
                            exit(1);
                        }
                        // 此时获得了要重定向的文件的文件描述符fd
                        dup2(fd, 0); // 输入重定向
                    }
                    break;
                case OUTPUT_REDIR:
                case APPEND_REDIR:
                    {
                        // 输出和追加都在这里处理
                        int flags = O_WRONLY | O_CREAT; // 不管是输出还是追加都要这两个选项
                        if(redirtype == APPEND_REDIR)
                            flags |= O_APPEND;
                        else
                            flags |= O_TRUNC;

                        int fd = open(redirFile, flags, 0666);
                        if(fd < 0)
                        {
                            perror("open error");
                            exit(1);
                        }
                        // 成功打开重定向文件，获得fd
                        dup2(fd, 1); // 1文件即为标准输出流，显示器
                    }
                    break;
                default:
                    printf("redirtype error\n");
                    break;
            }
            execvp(myargc[0], myargc);
            exit(0);
        }

        //parent
        int status = 0;
        pid_t ret = waitpid(id, &status, 0); // 这里传0.使用阻塞等待方式
        assert(ret != -1);
        if(ret > 0)
        {
            //等待成功，子进程资源被回收。子进程的退出信息已获取
            // 如果想要打印出来，在该分支打印即可
        }
    }

    return 0;
}
